/*
* MIT License
*
* Copyright (c) 2017 wen.gu <454727014@qq.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/***************************************************************************
* Name: tiny_plugin_manager.c
*
* Purpose: a manager to manage all plugins
*
* Developer:
*   wen.gu , 2018-08-22
*
* TODO:
*
***************************************************************************/

#include <stdio.h>
#include <string.h>

#include "tiny_plugin_manager.h"
#include "tiny_plugin.h"
#include "vos_mem.h"
#include "vos_sys.h"

#include "general_error.h"


#define LOG_TAG "PluginM"
#include "vos_log.h"

/***************************************************************************
*
* macro define
*
***************************************************************************/

/***************************************************************************
*
* data structure define
*
***************************************************************************/
typedef struct my_plugin_item_s
{
    tiny_plugin_t* plugin;
    void* dso; /** the handle of plugin shared library */
    struct my_plugin_item_s* next;
}my_plugin_item_t;


typedef struct my_plugin_manager_s
{
    my_plugin_item_t* head;
}my_plugin_manager_t;


/***************************************************************************
*
* inner API define
*
***************************************************************************/
static my_plugin_item_t* myPluginLoad_l(const char* name)
{
    my_plugin_item_t* item = NULL;
    GPHD dll = vosLoadDLL(name);

    if (dll)
    {
        pluginEntryFunc_t pluginEntry =
            (pluginEntryFunc_t)vosGetDLLSymbol(dll, TINY_PLUGIN_ENTRY_NAME);
        pluginHdrVerFunc_t verFunc =
            (pluginHdrVerFunc_t)vosGetDLLSymbol(dll, TINY_PLUGIN_VER_FUNC_NAME);

        if (pluginEntry && verFunc)
        {
            const char* ver = verFunc();

            if (strcmp(ver, TINY_PLUGIN_HEADER_VER) == 0)
            {/** todo, process the compatibility of master version */
                tiny_plugin_t* tp = pluginEntry();

                if (tp)
                {
                    item = (my_plugin_item_t*)memAlloc(sizeof(my_plugin_item_t));

                    if (item)
                    {
                        LOGD("load plugin(%s, ver: %s, author: %s) success "
                            "with plugin interface ver:%s\n",
                            tp->name, tp->version,
                            tp->author, TINY_PLUGIN_HEADER_VER);
                        item->plugin = tp;
                        item->dso = dll;
                        item->next = NULL;
                    }
                    else
                    {
                        LOGE("alloc plugin item memory failed\n");
                        
                        if (tp->shutdown)
                        {
                            tp->shutdown(tp);
                        }

                        vosFreeDLL(dll);
                    }
                }
                else
                {
                    vosFreeDLL(dll);
                    LOGE("create plugin instance by plugin "
                         "entry function failed\n");
                }
            }
            else
            {
                vosFreeDLL(dll);
                LOGE("version mismatch, host(%s), plugin(%s)\n",
                    TINY_PLUGIN_HEADER_VER, ver);
            }
        }
        else
        {
            vosFreeDLL(dll);
            LOGE("lib(%s) haven't entry func(%s, %s)\n",
                name, TINY_PLUGIN_ENTRY_NAME,
                TINY_PLUGIN_VER_FUNC_NAME);
        }

    }
    else
    {
        LOGE("load dll(%s) failed\n", name);
    }

    return item;
}

/***************************************************************************
*
* API define
*
***************************************************************************/

GPHD tfPluginManagerCreate()
{
    my_plugin_manager_t* mpm = (GPHD)memAlloc(sizeof(my_plugin_manager_t));

    if (mpm)
    {
        mpm->head = NULL;
    }

    return (GPHD)mpm;
}

void tfPluginManagerDestroy(GPHD pm)
{
    if (pm)
    {
        tfPluginManagerUnload(pm);
        
        memFree(pm);
    }
}


GS32 tfPluginManagerLoad(GPHD pm, const char** pluginList,
                         GU32 plCount, GBOL ignoreError)
{
    GS32 ret = G_ErrorBadParameter;

    if (pm && pluginList && (plCount > 0))
    {
        my_plugin_manager_t* mpm = (my_plugin_manager_t*)pm;

        if (!mpm->head)
        {
            GU32 i = 0;
            ret = G_OK;
            my_plugin_item_t* trail = NULL;
            for (; i < plCount; i++)
            {
                const char* plName = pluginList[i];

                if (plName && plName[0] != '\0')
                {
                    my_plugin_item_t* item = myPluginLoad_l(plName);

                    if (item)
                    {
                        if (!mpm->head)
                        {
                            mpm->head = item;
                            trail = item;
                        }
                        else
                        {
                            trail->next = item;
                            trail = item;
                        }
                    }
                    else if (ignoreError == GTRUE)
                    {
                        /** do nothing */
                    }
                    else
                    {
                        LOGE("load plugin(%s) failed\n", plName);
                        ret = G_ErrorInsufficientResources;
                        break;
                    }
                }
                else
                {
                    //todo process empty list
                }
            }
        }
        else
        {
            LOGE("plugin manager already loaded\n");
            ret = G_ErrorInvalidOperation;
        }
    }

    return ret;    
}

GS32 tfPluginManagerUnload(GPHD pm)
{
    GS32 ret = G_ErrorBadParameter;
    if (pm)
    {
        my_plugin_manager_t* mpm = (my_plugin_manager_t*)pm;
        my_plugin_item_t* head = mpm->head;

        while (head)
        {
            tiny_plugin_t* tp = head->plugin;

            if (tp && tp->shutdown)
            {
                tp->shutdown(tp);
            }

            if (head->dso)
            {
                vosFreeDLL(head->dso);
            }

            my_plugin_item_t* next = head->next;
            memFree(head);
            head = next;
        }

        mpm->head = NULL;
        ret = G_OK; /** todo refine me! */        
    }

    return ret;
}

GS32 tfPluginManagerFind(GPHD pm, tiny_component_t** comp,
                         const tiny_comp_create_param_t* param)
{
    GS32 ret = G_ErrorBadParameter;
    if (pm)
    {
        my_plugin_manager_t* mpm = (my_plugin_manager_t*)pm;

        if (comp && param)
        {
            ret = G_ErrorNotFound;
            my_plugin_item_t* head = mpm->head;
            while (head)
            {
                tiny_plugin_t* tp = head->plugin;

                if (tp && tp->createComponent)
                {
                    ret = tp->createComponent(tp, comp, param);

                    if (G_OK == ret)
                    {
                        break;
                    }
                }

                head = head->next;
            }
        }
    }

    return ret;
}